Tool

class Tool<TInput, TOutput, TContext>(val name: String, val description: String, val inputSerializer: KSerializer<TInput>, val outputSerializer: KSerializer<TOutput>, val executor: ToolExecutionContext<TContext>.(TInput) -> Flow<TOutput>, val needsApproval: suspend (input: TInput, options: ToolPredicateOptions<TContext>) -> Boolean? = null, val toModelOutput: (TOutput, ToolPredicateOptions<TContext>) -> ToolResultOutput? = null, val strict: Boolean = true, val inputExamples: List<String> = emptyList(), val onInputStart: suspend (streamingId: String) -> Unit? = null, val onInputDelta: suspend (streamingId: String, delta: String) -> Unit? = null, val onInputAvailable: suspend (toolCallId: String, input: TInput) -> Unit? = null, val metadata: Map<String, JsonElement> = emptyMap(), val providerExecuted: Boolean = false, val providerOptions: Map<String, JsonElement> = emptyMap())

A tool the LLM can call — application code constructs these via the top-level tool (single-value) or streamingTool (Flow) factories.

Per invariant I-2, tools use inputSchema (v6) — never parameters (v5, removed). Per I-8 tools are stateless: inputs in, outputs out, no shared mutable state.

Type Parameters

TInput

Application-defined input type, must be @Serializable. The model produces JSON matching its schema; the agent deserializes via inputSerializer before calling executor.

TOutput

Application-defined output type, must be @Serializable. Serialized into the model's tool-result message via outputSerializer, or summarized via toModelOutput if the raw output would be too verbose for the model.

TContext

Caller-supplied typed context propagated through the whole loop (set via callOptionsSchema, accessed inside the executor as this.context).

The internal executor always returns a Flow (the v6 alignment per tool.ts:68-71's AsyncIterable<OUTPUT> shape). Single-value tools built via tool wrap their suspend (TInput) -> TOutput body in a one-emission flow; streaming tools built via streamingTool pass their Flow body straight through. The agent loop's tool dispatcher collects with one-step lookahead — non-final emissions become StreamEvent.ToolResult(preliminary = true), the last emission is the final result that lands in the model's message log.

Constructors

Link copied to clipboard
constructor(name: String, description: String, inputSerializer: KSerializer<TInput>, outputSerializer: KSerializer<TOutput>, executor: ToolExecutionContext<TContext>.(TInput) -> Flow<TOutput>, needsApproval: suspend (input: TInput, options: ToolPredicateOptions<TContext>) -> Boolean? = null, toModelOutput: (TOutput, ToolPredicateOptions<TContext>) -> ToolResultOutput? = null, strict: Boolean = true, inputExamples: List<String> = emptyList(), onInputStart: suspend (streamingId: String) -> Unit? = null, onInputDelta: suspend (streamingId: String, delta: String) -> Unit? = null, onInputAvailable: suspend (toolCallId: String, input: TInput) -> Unit? = null, metadata: Map<String, JsonElement> = emptyMap(), providerExecuted: Boolean = false, providerOptions: Map<String, JsonElement> = emptyMap())

Properties

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

Few-shot example inputs encoded as JSON strings. Mirrors v6's tool.inputExamples (per historical parity gap #19). Disproportionate quality lift on small models — Gemma 4 E2B's tool-call accuracy notably improves with 2-3 in-tool examples. ToolSet.descriptors inlines these into the description as Example: <json> so every provider sees them (most don't expose a separate examples field).

Link copied to clipboard
val inputSerializer: KSerializer<TInput>
Link copied to clipboard
val metadata: Map<String, JsonElement>

Application-defined metadata bag. Mirrors v6's tool.metadata (per historical parity gap #34). Opaque to the loop — consumers (logger middleware, telemetry, host-side gating) can read it via ToolSet.byName. Examples: feature-flag key, billing tier, owning team. Empty by default.

Link copied to clipboard
Link copied to clipboard
val needsApproval: suspend (input: TInput, options: ToolPredicateOptions<TContext>) -> Boolean?

Approval gate. Per historical parity gap #17, the predicate receives ToolPredicateOptions (toolCallId + messages + experimental_context) — not just context. Lets the gate decide based on conversation history or call identity.

Link copied to clipboard
val onInputAvailable: suspend (toolCallId: String, input: TInput) -> Unit?

Lifecycle hook — fired after the streamed JSON has parsed successfully into the typed TInput, just before executor runs. Mirrors v6's tool.onInputAvailable.

Link copied to clipboard
val onInputDelta: suspend (streamingId: String, delta: String) -> Unit?

Lifecycle hook — fired as the model streams the tool's input JSON token-by-token (StreamEvent.ToolInputDelta). The accumulated input isn't yet valid JSON; this is for raw-character pre-warm.

Link copied to clipboard
val onInputStart: suspend (streamingId: String) -> Unit?

Lifecycle hook — fired when the model commits to calling this tool (StreamEvent.ToolInputStart arrives). Mirrors v6's tool.onInputStart (per historical parity gap #18). Lets a tool pre-warm: UI spinner, cache priming, etc.

Link copied to clipboard
val outputSerializer: KSerializer<TOutput>
Link copied to clipboard
Link copied to clipboard
val providerOptions: Map<String, JsonElement>

Provider-specific config for this tool, sent to the model on the wire (e.g. Anthropic cache_control). Distinct from metadata, which is host-side only. Mirrors v6's tool.providerOptions. Empty by default.

Link copied to clipboard

Per best practice #9: strict JSON schema enforcement is opt-in per tool, not global. Some providers reject schemas with format/regex constraints under strict mode; mark such tools strict = false.

Link copied to clipboard

Model-visible summary. Per historical parity gaps #17 + #14, the callback receives both the typed output AND ToolPredicateOptions; returns a structured ToolResultOutput (Text / Json / Error) instead of a bare String. The agent loop bridges each variant to the wire-format ContentPart.ToolResult.modelVisible (+ isError).